//
//  ckr_spec_cpp_specs.m
//  ckrSpec
//
//  Created by Keith Ray on 2006.10.16.
//  Copyright 2006 __MyCompanyName__. All rights reserved.
//

#import "ckr_spec_cpp_specs.h"
#import "ckr_spec_cpp.h"

#include <iostream>
#include <sstream>
#include <vector>
#include <deque>

#include <assert.h>

using namespace std;
using namespace ckr;

//  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

static void float_not_within_in_should_be_within_reports_failure();
static void float_within_in_should_be_within_reports_nothing();

static void cstring_not_within_in_should_be_within_reports_failure();
static void cstring_within_in_should_be_within_reports_nothing();
static void cstring_nulls_in_should_be_within_reports_failure();


static void float_within_in_should_not_be_within_reports_failure();
static void float_not_within_in_should_not_be_within_reports_nothing();

static void cstring_within_in_should_not_be_within_reports_failure();
static void cstring_not_within_in_should_not_be_within_reports_nothing();
static void cstring_nulls_in_should_not_be_within_reports_failure();

//  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

void run_within_comparison_specs()
{
	float_not_within_in_should_be_within_reports_failure();
	float_within_in_should_be_within_reports_nothing();

	cstring_not_within_in_should_be_within_reports_failure();
	cstring_within_in_should_be_within_reports_nothing();
	cstring_nulls_in_should_be_within_reports_failure();

	float_within_in_should_not_be_within_reports_failure();
	float_not_within_in_should_not_be_within_reports_nothing();

	cstring_within_in_should_not_be_within_reports_failure();
	cstring_not_within_in_should_not_be_within_reports_nothing();
	cstring_nulls_in_should_not_be_within_reports_failure();
}

// The perils of copying code.  I copied the implementation of AssertableValueCString::should_not_equal_impl for
// AssertableValueCString::should_be_less_than_impl.  First the spec failed because I forgot to change the operator in
//       std::string(other) == std::string(value)
// to less than... though I did remember to add that "not" I wanted to do.
//       not( std::string(other) < std::string(value) ) 
// Fixed that, but the spec still failed because I forgot to change the order of the variables to
//       not( std::string(value) < std::string(other) )
// Good thing those specs are there to catch my stupid mistakes.

// Gotta handle nulls now.

//  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

/*
	So far, we've got SHOULD_EQUAL, SHOULD_NOT_EQUAL, SHOULD_BE_LESS_THAN, SHOULD_BE_GREATER_THAN, and FAIL, with
	VALUE and CSTRING_VALUE (and templates and classes) to help for integral types, std::string and C strings.
	(User-created class types that conform to integral-style relational operators should also work, maybe write
	some specs to prove that later.)

	I realize that I need to assert the absense of strings in the output as well as the presence of strings. That is, 
	VALUE(2).SHOULD_EQUAL(2) should result in empty output. By combining specs for spec-failures and spec-non-failures
	in the same spec, I miss out in being explicit about the spec-non-failure behavior, and potentially don't get 
	good enough spec-coverage.
	
	I also figured out a syntax for specifying that a block of code throws an exception.
	
	THIS_CODE 
		...code....
	SHOULD_THROW( std::exception );
	
	Those macros expand to something like:
	
	try {
		...code...
		FAIL("did not throw expected exception 'std::exception'");
	} catch( std::exception& ex ) { };

#define THIS_CODE				try{
#define SHOULD_THROW(x)		FAIL("did not throw excpected exception '" #x "'"); } catch ( (x) ) { };

*/

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

/* refactor away duplication - code smell "Data Clump" - to "value_holder 
  template class and associated operator<< function
  
  I tried, but templates and c-style strings (character arrays) do NOT mix well. I could
  not come up with set of templates and template-specifications such that
  
  NameValue("aaa")  would compile.

*/

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

DECLARE_SPEC(Float_not_within_in_should_be_within);

void Float_not_within_in_should_be_within::spec()
{
	VALUE( 112.5 ).SHOULD_BE_WITHIN( 12.0, 13.0 ); // fail msg expected
}

void float_not_within_in_should_be_within_reports_failure()
{
	Float_not_within_in_should_be_within context;
	
	RecordingFailuresSpecListener listener;
	context.run_spec( & listener );

	std::string actual = listener.logStr();
	
	const std::string expected("112.5 (112.5) should be within 12.0 (12) through 13.0 (13) FAILED in Float_not_within_in_should_be_within; line");

	std::string::size_type foundPos = actual.find( expected, 0 );
	assert( foundPos != std::string::npos );
}

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

DECLARE_SPEC(Float_within_in_should_be_within);

void Float_within_in_should_be_within::spec()
{
	VALUE( 2.0 ).SHOULD_BE_WITHIN( 2.0, 3.0 ); // no msg expected
	VALUE( 6.0 ).SHOULD_BE_WITHIN( 5.0, 6.0 ); // no msg expected
}

void float_within_in_should_be_within_reports_nothing()
{
	Float_within_in_should_be_within context;
	
	RecordingFailuresSpecListener listener;
	context.run_spec( & listener );

	std::string actual = listener.logStr();
	
	const std::string expected;

	assert( expected == actual );
}

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

DECLARE_SPEC(CString_not_within_in_should_be_within);

void CString_not_within_in_should_be_within::spec()
{
	CSTRING_VALUE( "zzz" ).SHOULD_BE_WITHIN( "aaa", "ccc" ); // fail msg expected
}

void cstring_not_within_in_should_be_within_reports_failure()
{
	CString_not_within_in_should_be_within context;
	
	RecordingFailuresSpecListener listener;
	context.run_spec( & listener );

	std::string actual = listener.logStr();
	
	const std::string expected("\"zzz\" (zzz) should be within \"aaa\" (aaa) through \"ccc\" (ccc) FAILED in CString_not_within_in_should_be_within; line");

	std::string::size_type foundPos = actual.find( expected, 0 );
	assert( foundPos != std::string::npos );
}

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

DECLARE_SPEC(CString_within_in_should_be_within);

void CString_within_in_should_be_within::spec()
{
	CSTRING_VALUE( "2b" ).SHOULD_BE_WITHIN( "2b", "2c" ); // no msg expected
	CSTRING_VALUE( "3b" ).SHOULD_BE_WITHIN( "3a", "3c" ); // no msg expected
	CSTRING_VALUE( "6c" ).SHOULD_BE_WITHIN( "6a", "6c" ); // no msg expected
}

void cstring_within_in_should_be_within_reports_nothing()
{
	CString_within_in_should_be_within context;
	
	RecordingFailuresSpecListener listener;
	context.run_spec( & listener );

	std::string actual = listener.logStr();
	
	const std::string expected;

	assert( expected == actual );
}

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

DECLARE_SPEC(CString_nulls_in_should_be_within);

void CString_nulls_in_should_be_within::spec()
{
	const char* nullOne = NULL;
	const char* nullTwo = NULL;
	const char* nullThree = NULL;
	CSTRING_VALUE( nullOne ).SHOULD_BE_WITHIN( "2b", "2c" ); // fail msg expected
	CSTRING_VALUE( "3b" ).SHOULD_BE_WITHIN( nullOne, "3c" ); // fail msg expected
	CSTRING_VALUE( "6c" ).SHOULD_BE_WITHIN( "6a", nullOne ); // fail msg expected
	CSTRING_VALUE( nullTwo ).SHOULD_BE_WITHIN( "6a", nullOne ); // fail msg expected
	CSTRING_VALUE( nullTwo ).SHOULD_BE_WITHIN( nullOne, nullThree ); // fail msg expected
}

static void cstring_nulls_in_should_be_within_reports_failure()
{
	CString_nulls_in_should_be_within context;
	
	RecordingFailuresSpecListener listener;
	context.run_spec( & listener );

	std::string actual = listener.logStr();

	{
		const std::string expected1("nullOne ((NULL POINTER)) should be within \"2b\" (2b) through \"2c\" (2c) FAILED in CString_nulls_in_should_be_within; line");

		std::string::size_type foundPos1 = actual.find( expected1, 0 );
		assert( foundPos1 != std::string::npos );
	}
	{
		const std::string expected2("\"3b\" (3b) should be within nullOne ((NULL POINTER)) through \"3c\" (3c) FAILED in CString_nulls_in_should_be_within; line");

		std::string::size_type foundPos2 = actual.find( expected2, 0 );
		assert( foundPos2 != std::string::npos );
	}
	{
		const std::string expected3("\"6c\" (6c) should be within \"6a\" (6a) through nullOne ((NULL POINTER)) FAILED in CString_nulls_in_should_be_within; line");

		std::string::size_type foundPos3 = actual.find( expected3, 0 );
		assert( foundPos3 != std::string::npos );
	}
	{
		const std::string expected4("nullTwo ((NULL POINTER)) should be within \"6a\" (6a) through nullOne ((NULL POINTER)) FAILED in CString_nulls_in_should_be_within; line");

		std::string::size_type foundPos4 = actual.find( expected4, 0 );
		assert( foundPos4 != std::string::npos );
	}
	{
		const std::string expected5("nullTwo ((NULL POINTER)) should be within nullOne ((NULL POINTER)) through nullThree ((NULL POINTER)) FAILED in CString_nulls_in_should_be_within; line");

		std::string::size_type foundPos5 = actual.find( expected5, 0 );
		assert( foundPos5 != std::string::npos );
	}
}

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

DECLARE_SPEC(Float_within_in_should_not_be_within);

void Float_within_in_should_not_be_within::spec()
{
	VALUE( 1.0 ).SHOULD_NOT_BE_WITHIN( 1.0, 3.0 ); // fail msg expected
	VALUE( 2.0 ).SHOULD_NOT_BE_WITHIN( 1.0, 3.0 ); // fail msg expected
	VALUE( 3.0 ).SHOULD_NOT_BE_WITHIN( 1.0, 3.0 ); // fail msg expected
}

static void float_within_in_should_not_be_within_reports_failure()
{
	Float_within_in_should_not_be_within context;
	
	RecordingFailuresSpecListener listener;
	context.run_spec( & listener );

	std::string actual = listener.logStr();

	{
		const std::string expected1("1.0 (1) should not be within 1.0 (1) through 3.0 (3) FAILED in Float_within_in_should_not_be_within; line");

		std::string::size_type foundPos1 = actual.find( expected1, 0 );
		assert( foundPos1 != std::string::npos );
	}
	{
		const std::string expected2("2.0 (2) should not be within 1.0 (1) through 3.0 (3) FAILED in Float_within_in_should_not_be_within; line");

		std::string::size_type foundPos2 = actual.find( expected2, 0 );
		assert( foundPos2 != std::string::npos );
	}
	{
		const std::string expected3("3.0 (3) should not be within 1.0 (1) through 3.0 (3) FAILED in Float_within_in_should_not_be_within; line");

		std::string::size_type foundPos3 = actual.find( expected3, 0 );
		assert( foundPos3 != std::string::npos );
	}
}

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

DECLARE_SPEC(Float_not_within_in_should_not_be_within);

void Float_not_within_in_should_not_be_within::spec()
{
	VALUE( 0.0 ).SHOULD_NOT_BE_WITHIN( 1.0, 3.0 ); // no msg expected
	VALUE( 4.0 ).SHOULD_NOT_BE_WITHIN( 1.0, 3.0 ); // no msg expected
}

static void float_not_within_in_should_not_be_within_reports_nothing()
{
	Float_not_within_in_should_not_be_within context;
	
	RecordingFailuresSpecListener listener;
	context.run_spec( & listener );

	std::string actual = listener.logStr();
	
	const std::string expected;

	assert( expected == actual );
}

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

DECLARE_SPEC(CString_within_in_should_not_be_within);

void CString_within_in_should_not_be_within::spec()
{
	CSTRING_VALUE( "aa" ).SHOULD_NOT_BE_WITHIN( "aa", "cc" ); // fail msg expected
	CSTRING_VALUE( "bb" ).SHOULD_NOT_BE_WITHIN( "aa", "cc" ); // fail msg expected
	CSTRING_VALUE( "cc" ).SHOULD_NOT_BE_WITHIN( "aa", "cc" ); // fail msg expected
}

void cstring_within_in_should_not_be_within_reports_failure()
{
	CString_within_in_should_not_be_within context;
	
	RecordingFailuresSpecListener listener;
	context.run_spec( & listener );

	std::string actual = listener.logStr();

	{
		const std::string expected1("\"aa\" (aa) should not be within \"aa\" (aa) through \"cc\" (cc) FAILED in CString_within_in_should_not_be_within; line");

		std::string::size_type foundPos1 = actual.find( expected1, 0 );
		assert( foundPos1 != std::string::npos );
	}
	{
		const std::string expected2("\"bb\" (bb) should not be within \"aa\" (aa) through \"cc\" (cc) FAILED in CString_within_in_should_not_be_within; line");

		std::string::size_type foundPos2 = actual.find( expected2, 0 );
		assert( foundPos2 != std::string::npos );
	}
	{
		const std::string expected3("\"cc\" (cc) should not be within \"aa\" (aa) through \"cc\" (cc) FAILED in CString_within_in_should_not_be_within; line");

		std::string::size_type foundPos3 = actual.find( expected3, 0 );
		assert( foundPos3 != std::string::npos );
	}
}

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

DECLARE_SPEC(CString_not_within_in_should_not_be_within);

void CString_not_within_in_should_not_be_within::spec()
{
	CSTRING_VALUE( "aa" ).SHOULD_NOT_BE_WITHIN( "bb", "cc" ); // fail msg expected
	CSTRING_VALUE( "cc" ).SHOULD_NOT_BE_WITHIN( "aa", "bb" ); // fail msg expected
}

void cstring_not_within_in_should_not_be_within_reports_nothing()
{
	CString_not_within_in_should_not_be_within context;
	
	RecordingFailuresSpecListener listener;
	context.run_spec( & listener );

	std::string actual = listener.logStr();
	
	const std::string expected;

	assert( expected == actual );
}

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

DECLARE_SPEC(CString_nulls_in_should_not_be_within);

void CString_nulls_in_should_not_be_within::spec()
{
	const char* nullOne = NULL;
	const char* nullTwo = NULL;
	const char* nullThree = NULL;
	CSTRING_VALUE( nullOne ).SHOULD_NOT_BE_WITHIN( "2b", "2c" ); // fail msg expected
	CSTRING_VALUE( "3b" ).SHOULD_NOT_BE_WITHIN( nullOne, "3c" ); // fail msg expected
	CSTRING_VALUE( "6c" ).SHOULD_NOT_BE_WITHIN( "6a", nullOne ); // fail msg expected
	CSTRING_VALUE( nullTwo ).SHOULD_NOT_BE_WITHIN( "6a", nullOne ); // fail msg expected
	CSTRING_VALUE( nullTwo ).SHOULD_NOT_BE_WITHIN( nullOne, nullThree ); // fail msg expected
}

void cstring_nulls_in_should_not_be_within_reports_failure()
{
	CString_nulls_in_should_not_be_within context;
	
	RecordingFailuresSpecListener listener;
	context.run_spec( & listener );

	std::string actual = listener.logStr();

	{
		const std::string expected1("nullOne ((NULL POINTER)) should not be within \"2b\" (2b) through \"2c\" (2c) FAILED in CString_nulls_in_should_not_be_within; line");

		std::string::size_type foundPos1 = actual.find( expected1, 0 );
		assert( foundPos1 != std::string::npos );
	}
	{
		const std::string expected2("\"3b\" (3b) should not be within nullOne ((NULL POINTER)) through \"3c\" (3c) FAILED in CString_nulls_in_should_not_be_within; line");

		std::string::size_type foundPos2 = actual.find( expected2, 0 );
		assert( foundPos2 != std::string::npos );
	}
	{
		const std::string expected3("\"6c\" (6c) should not be within \"6a\" (6a) through nullOne ((NULL POINTER)) FAILED in CString_nulls_in_should_not_be_within; line");

		std::string::size_type foundPos3 = actual.find( expected3, 0 );
		assert( foundPos3 != std::string::npos );
	}
	{
		const std::string expected4("nullTwo ((NULL POINTER)) should not be within \"6a\" (6a) through nullOne ((NULL POINTER)) FAILED in CString_nulls_in_should_not_be_within; line");

		std::string::size_type foundPos4 = actual.find( expected4, 0 );
		assert( foundPos4 != std::string::npos );
	}
	{
		const std::string expected5("nullTwo ((NULL POINTER)) should not be within nullOne ((NULL POINTER)) through nullThree ((NULL POINTER)) FAILED in CString_nulls_in_should_not_be_within; line");

		std::string::size_type foundPos5 = actual.find( expected5, 0 );
		assert( foundPos5 != std::string::npos );
	}
}

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

